﻿using System;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Xsl;
using System.Web;

namespace Landscape
{
    class Template
    {
        public XmlDocument TemplateDom{
            get;
            set;
        }
        public object TemplateCompiledTransform{
            get;
            set;
        }
        public string TemplatePath {
            get;
            set;
        }
        public List<string> DependenciesPaths {
            get;
            set;
        }
        public TemplateOutputSettings OutputSettings;
        public OutputMethods OutputMethod = OutputMethods.xml;

        public Template(string xmlTemplatePath)
        {
            TemplateDom = new XmlDocument();
            TemplateDom.Load(xmlTemplatePath);

            TemplatePath = xmlTemplatePath;
     
            DependenciesPaths = new List<string>();
            DependenciesPaths.Add(TemplatePath);
            ResolveDependencies(TemplateDom.GetElementsByTagName("include", Context.xslNamespace));
            ResolveDependencies(TemplateDom.GetElementsByTagName("import", Context.xslNamespace));
        }

        /// <summary>
        /// Creates an instance of a Template object
        /// using an xml document
        /// </summary>
        /// <typeparam name="T">The Specific type of the Template instance</typeparam>
        /// <param name="templatePath">The location of the xml document</param>
        /// <returns></returns>
        public static T GetTemplate<T>(string templatePath) where T : Template {
            if (Context.enableCaching) {
                if (HttpRuntime.Cache[templatePath] != null) {
                    T cached = (T)HttpRuntime.Cache[templatePath];

                    return cached;
                }
            }

            T template = (T)Activator.CreateInstance(typeof(T), new object[] { templatePath });

            // saves object into cache
            if (Context.enableCaching) {

                HttpRuntime.Cache.Insert(templatePath, template, new System.Web.Caching.CacheDependency(template.DependenciesPaths.ToArray()));
            }

            return template;
        }


        /// <summary>
        /// Resolve smart include paths into the current template and add the processed paths to the dependencies so the template cache expires when one of the dependencies is touched
        /// </summary>
        /// <param name="depNodes">The nodes to check against for smart includes</param>
        private void ResolveDependencies(XmlNodeList depNodes) {
            if (depNodes != null && depNodes.Count > 0) {
                foreach (XmlNode depNode in depNodes) { 
                    XmlNode depHrefNode = depNode.Attributes.GetNamedItem("href");
                    if (depHrefNode != null && !string.IsNullOrEmpty(depHrefNode.Value)) {
                        Uri depUri = new TemplateXmlUrlResolver(Context.Instance.includePaths).ResolveUri(new Uri(TemplatePath), depHrefNode.Value);

                        // add dependency and resolve href into template
                        if (depUri != null) {
                            DependenciesPaths.Add(depUri.AbsolutePath);
                            depHrefNode.Value = depUri.AbsolutePath;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Configure the selected template output settings using its xsl:output node, when defined.
        /// </summary>
        /// <param name="overwrite">Wether to ignore xsl:output and configure the template using Template.OutputSettings</param>
        protected void ConfigureOutput(bool overwrite) {
            XmlElement outputTag = TemplateDom.CreateElement("xsl", "output", Context.xslNamespace);
            outputTag.SetAttribute("version", OutputSettings.Version);            
            OutputMethods rawMethod = OutputSettings.Method == OutputMethods.xhtml ? OutputMethods.xml : OutputSettings.Method;
            outputTag.SetAttribute("method", Enum.GetName(typeof(OutputMethods), rawMethod).ToLower());
            outputTag.SetAttribute("omit-xml-declaration", (OutputSettings.OmitXmlDeclaration == true ? "yes" : "no"));
            outputTag.SetAttribute("standalone", (OutputSettings.Standalone == true ? "yes" : "no"));
            outputTag.SetAttribute("indent", (OutputSettings.Indent  == true ? "yes" : "no"));
            outputTag.SetAttribute("encoding", OutputSettings.Encoding);

            if (!string.IsNullOrEmpty(OutputSettings.DocTypePublic)) {
                outputTag.SetAttribute("doctype-public", OutputSettings.DocTypePublic);
            }

            if (!string.IsNullOrEmpty(OutputSettings.DocTypeSystem)) {
                outputTag.SetAttribute("doctype-system", OutputSettings.DocTypeSystem);
            }

            XmlNodeList xslOutputTags = TemplateDom.GetElementsByTagName("output", Context.xslNamespace);
            if (xslOutputTags.Count > 0) {
                XmlNode outputNode = xslOutputTags.Item(0);
                // replace defined settings
                if (overwrite == true) {
                    TemplateDom.DocumentElement.ReplaceChild(outputTag, outputNode);

                // configure template using the defined xsl output
                } else {
                    OutputSettings = new TemplateOutputSettings(outputNode);
                }
            } else {
                XmlNode xslOutputLastPrecedingSibling = TemplateDom.DocumentElement.FirstChild;

                // insert just after the last import node, if any
                XmlNodeList xslImports = TemplateDom.GetElementsByTagName("import", Context.xslNamespace);
                if (xslImports.Count > 0) {
                    xslOutputLastPrecedingSibling = xslImports.Item(xslImports.Count - 1);
                    TemplateDom.DocumentElement.InsertAfter(outputTag, xslOutputLastPrecedingSibling);
                } else {
                    TemplateDom.DocumentElement.InsertBefore(outputTag, xslOutputLastPrecedingSibling);
                }
            }
        }

        // TODO: find a smart way to implement the static GetTemplate method here returning the desired object type
        // public static object GetTemplate(string templatePath, System.Type returnType);
    }
}
